package lookas.test;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import javax.sql.DataSource;

import net.sf.json.JSONArray;
import net.sf.json.JSONObject;

import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;

public class PermutationMain {

    static final Log                 log = LogFactory.getLog(PermutationMain.class);
    private ThreadPoolExecutor       executor;
    private Map<String, Permutation> map;
    private BasicDataSource          ds;

    public static void main(String[] args) throws Exception {
        new PermutationMain().main();
    }

    public void main() throws IOException {
        ds = new BasicDataSource();
        ds.setUrl("jdbc:derby://localhost:1527/cubedb");
        ds.setDriverClassName("org.apache.derby.jdbc.ClientDriver");
        ds.setUsername("app");
        ds.setPassword("app");

        FileReader file = new FileReader("src/test/resources/cube");
        BufferedReader buf = new BufferedReader(file);
        String line;
        StringBuilder s = new StringBuilder();
        while ((line = buf.readLine()) != null) {
            s.append(line);
        }
        file.close();
        JSONObject o = JSONObject.fromString(s.toString());
        map = init(o);

        BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
        executor = new ThreadPoolExecutor(2, 4, 30, TimeUnit.SECONDS, queue);

        SimpleJdbcTemplate t = new SimpleJdbcTemplate(ds);
        List<Map<String, Object>> ids = t.queryForList("select p1.id from PERMUTATIONS p1 "
                + "left join PERMUTATIONS p2 on p1.id=p2.parent " + "group by p1.id having count(p1.id)<6");
        for (Map<String, Object> map : ids) {
            execute((Integer) map.get("ID"));
        }
    }

    private void execute(int i) {
        PermutationRun run = new PermutationRun(i);
        run.setMain(this);
        run.setMap(map);
        run.setDataSource(ds);
        executor.execute(run);
    }

    public void execute(int i, Map<String, Object> data) {
        PermutationRun run = new PermutationRun(i);
        run.setMain(this);
        run.setMap(map);
        run.setParent((Permutation) data.get("p"));
        run.setMove((String) data.get("move"));
        run.setDataSource(ds);
        executor.execute(run);
    }

    public static class PermutationRun implements Runnable {

        private Map<String, Permutation> map;
        private Map<String, Object>      data;
        private Permutation              parent;
        private SimpleJdbcTemplate       template;
        private String                   move;
        private PermutationMain          main;

        public PermutationRun(int parent) {
            data = new HashMap<String, Object>();
            data.put("parent", parent);
        }

        public void run() {
            for (Entry<String, Permutation> e : map.entrySet()) {
                Permutation p = parent.multiply(e.getValue());
                data.put("p", p);
                data.put("move", move + e.getKey());
                data.put("permutation", p.toString());
                data.put("cycles", p.asCycles().toString());

                PreparedStatementCreator psc = new PreparedStatementCreator() {

                    public PreparedStatement createPreparedStatement(Connection con) throws SQLException {

                        PreparedStatement ps = con.prepareStatement(
                                "insert into permutations(permutation, cycles, parent)" + "values (?, ?, ?)",
                                Statement.RETURN_GENERATED_KEYS);
                        ps.setString(1, data.get("permutation").toString());
                        ps.setString(2, data.get("cycles").toString());
                        ps.setInt(3, (Integer) data.get("parent"));
                        return ps;
                    }
                };
                Number key;
                try {
                    KeyHolder keyHolder = new GeneratedKeyHolder();
                    template.getJdbcOperations().update(psc, keyHolder);
                    key = keyHolder.getKey();
                    main.execute(key.intValue(), data);
                } catch (Exception ex) {
                    log.debug(ex);
                    key = template.queryForObject("select id from permutations " + "where permutation=:permutation",
                            Integer.class, data);
                }
                data.put("id", key);
                try {
                    template.update("insert into moves(move, perm_id)" + "values (:move, :id)", data);
                } catch (Exception ex) {
                    log.warn(ex);
                }
            }
        }

        public void setMain(PermutationMain main) {
            this.main = main;
        }

        public void setMap(Map<String, Permutation> map) {
            this.map = map;
        }

        public void setParent(Permutation parent) {
            this.parent = parent;
        }

        public void setMove(String move) {
            this.move = move;
        }

        public void setDataSource(DataSource ds) {
            template = new SimpleJdbcTemplate(ds);
            if (parent == null && move == null) {
                Map<String, Object> m = template.queryForMap("select m.move, p.permutation "
                        + "from MOVES m left join PERMUTATIONS p on m.PERM_ID=p.ID "
                        + "where m.ID=(select min(id) from MOVES where PERM_ID=:parent)", data);
                String p = (String) m.get("PERMUTATION");
                move = (String) m.get("MOVE");
                parent = Permutation.fromString(p);
            }
        }
    }

    @SuppressWarnings("unchecked")
    public Map<String, Permutation> init(JSONObject o) {
        Map<String, Permutation> map = new HashMap<String, Permutation>();
        {
            String s = "U";
            JSONArray array = o.getJSONArray(s);
            map.put(s, Permutation.fromCycles(50, JSONArray.toList(array)));
        }
        {
            String s = "F";
            JSONArray array = o.getJSONArray(s);
            map.put(s, Permutation.fromCycles(50, JSONArray.toList(array)));
        }
        {
            String s = "R";
            JSONArray array = o.getJSONArray(s);
            map.put(s, Permutation.fromCycles(50, JSONArray.toList(array)));
        }
        {
            String s = "B";
            JSONArray array = o.getJSONArray(s);
            map.put(s, Permutation.fromCycles(50, JSONArray.toList(array)));
        }
        {
            String s = "D";
            JSONArray array = o.getJSONArray(s);
            map.put(s, Permutation.fromCycles(50, JSONArray.toList(array)));
        }
        {
            String s = "L";
            JSONArray array = o.getJSONArray(s);
            map.put(s, Permutation.fromCycles(50, JSONArray.toList(array)));
        }
        return map;
    }
}
